/* 
 * Cashbox is the legal property of its developers, whose names are listed in the copyright file included
 * with this source distribution.
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#import "CBOFXImporter.h"
#import <Libofx/libofx.h>
#import <Cashbox/CBAttribute.h>

int ofx_proc_account_cb(struct OfxAccountData data, void * account_data);
int ofx_proc_transaction_cb(struct OfxTransactionData data, void * transaction_data);

@interface CBOFXImporter (Private)
- (void)copyDTDsIfNeeded;
- (void)addUnknownAccountTypeError;
@end

@implementation CBOFXImporter

+ (void)load {
	[CBImporter registerImporter:self];
}

+ (NSArray *)canHandleExtensions {
	return [NSArray arrayWithObjects:@"ofx", @"qfx", @"ofc", nil];
}

- (id)init {
	if (self = [super init]) {
		accountReferences = [[NSMutableDictionary alloc] init];
	}
	return self;
}

- (void)dealloc {
	[accountReferences release];
	[super dealloc];
}

- (void)addDataFromFile:(NSString *)file {
	[self copyDTDsIfNeeded];
	
	LibofxContextPtr context = libofx_get_new_context();

	NSArray *data = [NSArray arrayWithObjects: self, accountReferences, nil];
	ofx_set_account_cb(context, ofx_proc_account_cb, data);
	ofx_set_transaction_cb(context, ofx_proc_transaction_cb, data);

	libofx_proc_file(context, [file UTF8String], AUTODETECT);	
	libofx_free_context(context);
}

- (void)addUnknownAccountTypeError {
	NSString *errorString = NSLocalizedString(@"The file contains account information that Cashbox cannot handle.", nil);
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorString forKey:NSLocalizedDescriptionKey];
	[super addError:[NSError errorWithDomain:@"CBImporterDomain" code:CBCautionaryImporterError userInfo:userInfo]];
}

- (void)copyDTDsIfNeeded {
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSString *appSupportFolder = [cashbox cashboxApplicationSupportFolder];
	NSString *dtdFolder = [appSupportFolder stringByAppendingPathComponent:@"dtd"];
	BOOL isDirectory;
	
	if (![fileManager fileExistsAtPath:dtdFolder isDirectory:&isDirectory]) {
		if ([fileManager createDirectoryAtPath:dtdFolder attributes:nil]) { isDirectory = TRUE; }
		else { FSLog(@"could not copy dtd files.  could not create directory"); }
	}

	if (isDirectory) {
		// copy files
		NSString *resourceDTDFolder = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"dtd"];
		NSArray *files = [fileManager directoryContentsAtPath:resourceDTDFolder];
		NSString *dtdFile;
		NSEnumerator *fileEnumerator = [files objectEnumerator];
		while (dtdFile = [fileEnumerator nextObject]) {
			NSString *copyFrom = [resourceDTDFolder stringByAppendingPathComponent:dtdFile];
			NSString *copyTo = [dtdFolder stringByAppendingPathComponent:dtdFile];
			if (![fileManager fileExistsAtPath:copyTo]) { [fileManager copyPath:copyFrom toPath:copyTo handler:nil]; }
		}
	} else {
		FSLog(@"could not copy dtd files.  ~/Library/Application Support/Cashbox/dtd isn't a folder.");			
	}
}

@end

int ofx_proc_transaction_cb(struct OfxTransactionData data, void * transaction_data) {
	
	id accountReference = nil;
	CBOFXImporter *importer = [(NSArray *)transaction_data objectAtIndex:0];
	NSMutableDictionary *accountReferences = [(NSArray *)transaction_data objectAtIndex:1];

	if(data.account_id_valid == true) {
		NSString *accountID = [NSString stringWithCString:data.account_id];
		accountReference = [accountReferences objectForKey:accountID];
	}	
	
	id date = [NSNull null];
	id description = [NSNull null];
	id withdrawal = [NSNull null];
	id deposit = [NSNull null];
	id checkNumber = [NSNull null];
	BOOL cleared;
	
	if (data.name_valid == true) { description = [NSString stringWithCString:data.name]; }
	if (data.amount_valid == true && data.amount >= 0) { deposit = [NSNumber numberWithLongLong:roundtol(data.amount * 100)]; }
	if (data.amount_valid == true && data.amount < 0) { withdrawal = [NSNumber numberWithLongLong:roundtol(data.amount * 100)]; }

	// date
	if (data.date_initiated_valid == true) { date = [NSDate dateWithTimeIntervalSince1970:data.date_initiated]; }
	else if (data.date_posted_valid == true) { date = [NSDate dateWithTimeIntervalSince1970:data.date_posted]; }
	else if (data.date_funds_available == true) { date = [NSDate dateWithTimeIntervalSince1970:data.date_funds_available]; }
	else { date = [NSDate date]; }

	// cleared state
	if (data.date_posted_valid == true) {
		NSDate *postedDate = [NSDate dateWithTimeIntervalSince1970:data.date_posted];
		cleared = ([postedDate compare:[NSDate date]] == NSOrderedAscending);
	}

	NSMutableDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
		description, [NSNumber numberWithShort:CBDescriptionRole],
		checkNumber, [NSNumber numberWithShort:CBCheckNumberRole],
		deposit, [NSNumber numberWithShort:CBPositiveCashflowRole],
		withdrawal, [NSNumber numberWithShort:CBNegativeCashflowRole], nil];
	
	[importer addTransactionToAccount:accountReference
					   withAttributes:attributes
								 date:date
						   reconciled:NO
							  cleared:cleared
							labelName:nil
							reference:NULL];
	
	return 0;
}

int ofx_proc_account_cb(struct OfxAccountData data, void * account_data) {
	NSString *accountName = [NSString stringWithCString:data.account_name];
	NSString *accountID = [NSString stringWithCString:data.account_id];
	NSArray *keys = nil;
	
	CBOFXImporter *importer = [(NSArray *)account_data objectAtIndex:0];
	NSMutableDictionary *accountReferences = [(NSArray *)account_data objectAtIndex:1];
	
	if (data.account_type_valid == true) {
		switch (data.account_type) {
			case OFX_CHECKING:
				keys = [NSArray arrayWithObjects:
					[NSNumber numberWithShort:CBDescriptionRole],
					[NSNumber numberWithShort:CBNegativeCashflowRole],
					[NSNumber numberWithShort:CBPositiveCashflowRole],
					[NSNumber numberWithShort:CBCheckNumberRole], nil];
				break;
			case OFX_SAVINGS:
			case OFX_CREDITCARD:
				keys = [NSArray arrayWithObjects:
					[NSNumber numberWithShort:CBDescriptionRole],
					[NSNumber numberWithShort:CBNegativeCashflowRole],
					[NSNumber numberWithShort:CBPositiveCashflowRole], nil];				
				break;
			case OFX_MONEYMRKT:
			case OFX_CREDITLINE:
			case OFX_CMA:
			case OFX_INVESTMENT:
			default:
				[importer addUnknownAccountTypeError];
				break;
		}
	}
	
	NSString *bankID = nil;
	NSString *branchID = nil;
	NSString *accountNumber = nil;
	if (data.bank_id_valid) { bankID = [NSString stringWithCString:data.bank_id]; }
	if (data.branch_id_valid) { branchID = [NSString stringWithCString:data.branch_id]; }
	if (data.account_number_valid) { accountNumber = [NSString stringWithCString:data.account_number]; }

	NSString *additionalInfo = @"";
	
	if (accountName && accountID && keys) {
		id reference = nil;
		
		[importer addAccountWithName:accountName
					  additionalInfo:additionalInfo
								keys:keys
						   reference:&reference];
		
		[accountReferences setObject:reference forKey:accountID];
	}		
	
	return 0;		
}
